Skip to content

Rework journeys#592

Merged
Blaumaus merged 4 commits into
mainfrom
improvement/journeys
Jul 16, 2026
Merged

Rework journeys#592
Blaumaus merged 4 commits into
mainfrom
improvement/journeys

Conversation

@Blaumaus

@Blaumaus Blaumaus commented Jul 16, 2026

Copy link
Copy Markdown
Member

Changes

If applicable, please describe what changes were made in this pull request.

Community Edition support

  • Your feature is implemented for the Swetrix Community Edition
  • This PR only updates the Cloud (Enterprise) Edition code (e.g. Paddle webhooks, blog, payouts, etc.)

Database migrations

  • Clickhouse / MySQL migrations added for this PR
  • No table schemas changed in this PR

Documentation

  • You have updated the documentation according to your PR
  • This PR did not change any publicly documented endpoints

Summary by CodeRabbit

  • New Features

    • Enhanced Journeys analytics with continuation metrics (continuedPast), total path counts, and per-length histograms, plus richer node/link breakdowns.
    • Updated Sankey visualization with “Other paths” and “Exited” nodes, terminal handling, improved tooltips, and coverage indicators.
    • Journeys view now syncs settings with URL search parameters for shareable charts.
    • Expanded UI text, labels, and added a “learn more” documentation link.
  • Bug Fixes

    • Funnel results no longer fail when time-to-convert data is unavailable.
    • Journey node/link details degrade gracefully if supplementary graph details can’t be fetched.
    • Funnel top source/country tooltips now prefer v2 breakdowns with v1 fallback.

@Blaumaus Blaumaus self-assigned this Jul 16, 2026
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2034fb1d-63ff-4bab-9f52-3356c3aa3305

📥 Commits

Reviewing files that changed from the base of the PR and between a29a670 and d5f3ced.

📒 Files selected for processing (3)
  • backend/apps/cloud/src/analytics/interfaces/index.ts
  • backend/apps/community/src/analytics/interfaces/index.ts
  • web/app/api/api.server.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • web/app/api/api.server.ts
  • backend/apps/cloud/src/analytics/interfaces/index.ts

📝 Walkthrough

Walkthrough

Journey analytics now reports continuation and path-length coverage, exposes node/link breakdowns, and renders these details in an expanded Sankey and table UI. Journey controls persist in URL parameters, while funnel and journey endpoints tolerate selected analytics lookup failures.

Changes

Journey analytics expansion

Layer / File(s) Summary
Journey response contracts
backend/apps/cloud/src/analytics/interfaces/index.ts, backend/apps/community/src/analytics/interfaces/index.ts, web/app/api/api.server.ts
Journey responses add continuedPast, totalPaths, length histograms, node details, and link details with source and country breakdowns.
Journey path and graph aggregation
backend/apps/cloud/src/analytics/analytics.service.ts, backend/apps/community/src/analytics/analytics.service.ts
ClickHouse queries retain full paths, calculate continuation and histogram data, emit synthetic exit links, and map node/link rows into structured details.
Funnel resilience and endpoint wiring
backend/apps/cloud/src/analytics/analytics.controller.ts, backend/apps/cloud/src/analytics/v2/analytics-v2.service.ts, backend/apps/community/src/analytics/analytics.controller.ts, backend/apps/community/src/analytics/v2/analytics-v2.service.ts, backend/apps/community/src/analytics/analytics.service.ts
Funnel time-to-convert failures are logged and suppressed; journey flow and graph details load concurrently, with empty details returned when detail retrieval fails.
Journey visualization and navigation
web/app/pages/Project/tabs/Journeys/JourneysView.tsx, web/app/pages/Project/View/ViewProject.helpers.tsx, web/public/locales/en.json
The journeys UI adds exit and other-path nodes, coverage reporting, richer tooltips, URL-synchronized controls, chart/table switching, and updated localized copy.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant JourneysView
  participant AnalyticsController
  participant AnalyticsService
  participant ClickHouse
  JourneysView->>AnalyticsController: request journeys
  AnalyticsController->>AnalyticsService: fetch flow and node/link details
  AnalyticsService->>ClickHouse: execute journey aggregation queries
  ClickHouse-->>AnalyticsService: return paths, histogram, nodes, and links
  AnalyticsService-->>AnalyticsController: return journey data
  AnalyticsController-->>JourneysView: render flow and details
Loading

Possibly related PRs

  • Swetrix/swetrix#510: Both changes modify funnel tooltip plumbing for per-step source and country data.
  • Swetrix/swetrix#588: Both changes modify the journeys analytics API and Sankey graph construction.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The template sections are present, but the Changes section is left as placeholder text and does not describe the actual updates. Add a brief Changes summary covering the journeys analytics and UI updates, and keep the support/migrations/docs checkboxes as filled.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed It clearly references the journeys feature being reworked, which matches the main scope even though it's broad.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch improvement/journeys

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/apps/cloud/src/analytics/analytics.service.ts (1)

1206-1234: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add a deterministic tie-breaker to journey ranking

getJourneys and getJourneyNodeDetails are called together in the same response, so tied value counts can make the journeys list and Sankey details pick different paths. Add path ASC to both row_number() ORDER BY clauses in:

  • backend/apps/cloud/src/analytics/analytics.service.ts#L1206-L1234
  • backend/apps/cloud/src/analytics/analytics.service.ts#L1351-L1363
  • backend/apps/community/src/analytics/analytics.service.ts#L1009-L1037
  • backend/apps/community/src/analytics/analytics.service.ts#L1154-L1166
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/apps/cloud/src/analytics/analytics.service.ts` around lines 1206 -
1234, Make journey ranking deterministic by adding path ASC as the secondary
sort key after value DESC in both row_number() ORDER BY clauses at
backend/apps/cloud/src/analytics/analytics.service.ts:1206-1234 and :1351-1363,
and backend/apps/community/src/analytics/analytics.service.ts:1009-1037 and
:1154-1166. Apply the same tie-breaker consistently in getJourneys and
getJourneyNodeDetails.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/apps/cloud/src/analytics/v2/analytics-v2.service.ts`:
- Around line 1707-1720: Replace the this.logger.log call in the
getFunnelTimeToConvert catch block with the error-level logging method,
preserving the existing reason and contextual message so failures are emitted in
production.

In `@web/app/pages/Project/tabs/Journeys/JourneysView.tsx`:
- Around line 1197-1200: Update the labelTextColor configuration in the journey
node rendering to choose a contrasting color for light-theme nodes based on each
node’s kind or background luminance, ensuring labels remain readable on
saturated blue, purple, and similar backgrounds while preserving the existing
dark-theme behavior.
- Around line 703-725: Remove the global hiddenPaths calculation and its
hidden-path count from the node.kind === 'other' tooltip in JourneysView. Keep
the existing “Other paths” label, step, and session count unchanged; do not
display a count that is not scoped to the current step.
- Around line 260-277: Add an effect in JourneysView that watches searchParams
and synchronizes steps, journeysCount, and view from the current URL values
using the same clamping and defaults as their initializers. Ensure changed query
parameters update the local controls and downstream debounced/fetched data while
preserving existing values for valid chart/table views.

---

Outside diff comments:
In `@backend/apps/cloud/src/analytics/analytics.service.ts`:
- Around line 1206-1234: Make journey ranking deterministic by adding path ASC
as the secondary sort key after value DESC in both row_number() ORDER BY clauses
at backend/apps/cloud/src/analytics/analytics.service.ts:1206-1234 and
:1351-1363, and
backend/apps/community/src/analytics/analytics.service.ts:1009-1037 and
:1154-1166. Apply the same tie-breaker consistently in getJourneys and
getJourneyNodeDetails.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7dfe59e9-444f-45e5-a67c-fb485f82e827

📥 Commits

Reviewing files that changed from the base of the PR and between 826558a and a29a670.

📒 Files selected for processing (12)
  • backend/apps/cloud/src/analytics/analytics.controller.ts
  • backend/apps/cloud/src/analytics/analytics.service.ts
  • backend/apps/cloud/src/analytics/interfaces/index.ts
  • backend/apps/cloud/src/analytics/v2/analytics-v2.service.ts
  • backend/apps/community/src/analytics/analytics.controller.ts
  • backend/apps/community/src/analytics/analytics.service.ts
  • backend/apps/community/src/analytics/interfaces/index.ts
  • backend/apps/community/src/analytics/v2/analytics-v2.service.ts
  • web/app/api/api.server.ts
  • web/app/pages/Project/View/ViewProject.helpers.tsx
  • web/app/pages/Project/tabs/Journeys/JourneysView.tsx
  • web/public/locales/en.json

Comment on lines 1707 to 1720
(async () => {
timeToConvert = await this.analyticsService.getFunnelTimeToConvert(
steps,
params,
filters.filtersQuery,
)
try {
timeToConvert = await this.analyticsService.getFunnelTimeToConvert(
steps,
params,
filters.filtersQuery,
)
} catch (reason) {
this.logger.log(
reason,
'AnalyticsV2Service -> getFunnelTimeToConvert',
)
}
})(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Failure is logged with .log(), which is silently dropped in production.

Based on learnings, AppLoggerService.log(value, route, forceLog=false) only emits when isDevelopment is true or forceLog is explicitly set — so a getFunnelTimeToConvert failure here produces no log entry in production, unlike the V1 controllers' equivalent handling of this same failure (which use this.logger.error(...)). This weakens the ability to detect/debug the very failures this resilience wrapping is meant to tolerate.

🔧 Proposed fix
         } catch (reason) {
-          this.logger.log(
-            reason,
-            'AnalyticsV2Service -> getFunnelTimeToConvert',
-          )
+          this.logger.error(
+            reason,
+            'AnalyticsV2Service -> getFunnelTimeToConvert',
+          )
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
(async () => {
timeToConvert = await this.analyticsService.getFunnelTimeToConvert(
steps,
params,
filters.filtersQuery,
)
try {
timeToConvert = await this.analyticsService.getFunnelTimeToConvert(
steps,
params,
filters.filtersQuery,
)
} catch (reason) {
this.logger.log(
reason,
'AnalyticsV2Service -> getFunnelTimeToConvert',
)
}
})(),
(async () => {
try {
timeToConvert = await this.analyticsService.getFunnelTimeToConvert(
steps,
params,
filters.filtersQuery,
)
} catch (reason) {
this.logger.error(
reason,
'AnalyticsV2Service -> getFunnelTimeToConvert',
)
}
})(),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/apps/cloud/src/analytics/v2/analytics-v2.service.ts` around lines
1707 - 1720, Replace the this.logger.log call in the getFunnelTimeToConvert
catch block with the error-level logging method, preserving the existing reason
and contextual message so failures are emitted in production.

Source: Learnings

Comment on lines +260 to +277
const [searchParams, setSearchParams] = useSearchParams()

const [steps, setSteps] = useState(DEFAULT_STEPS)
const [journeysCount, setJourneysCount] = useState(DEFAULT_JOURNEYS)
const [steps, setSteps] = useState(() => {
const value = Number(searchParams.get('steps'))
return Number.isFinite(value) && value ? clampSteps(value) : DEFAULT_STEPS
})
const [journeysCount, setJourneysCount] = useState(() => {
const value = Number(searchParams.get('journeys'))
return Number.isFinite(value) && value
? clampJourneys(value)
: DEFAULT_JOURNEYS
})
const [view, setView] = useState<'chart' | 'table'>(() =>
searchParams.get('view') === 'table' ? 'table' : 'chart',
)
const [debouncedParams, setDebouncedParams] = useState({
steps: DEFAULT_STEPS,
journeys: DEFAULT_JOURNEYS,
steps,
journeys: journeysCount,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Synchronize local controls when the URL changes after mount.

These initializers only read searchParams once. Browser navigation or in-app navigation to another steps, journeys, or view query leaves the controls and fetched data stale relative to the URL. Add an effect that parses and applies changed search parameters.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/app/pages/Project/tabs/Journeys/JourneysView.tsx` around lines 260 - 277,
Add an effect in JourneysView that watches searchParams and synchronizes steps,
journeysCount, and view from the current URL values using the same clamping and
defaults as their initializers. Ensure changed query parameters update the local
controls and downstream debounced/fetched data while preserving existing values
for valid chart/table views.

Comment on lines +703 to +725
if (node.kind === 'other') {
const hiddenPaths = Math.max(
(coverage?.totalPaths || 0) - (coverage?.drawnPaths || 0),
0,
)

return (
<div className={tooltipContainerClassName}>
<div className='flex items-center justify-between gap-6'>
<div className='flex min-w-0 items-center gap-1.5'>
<span className='truncate font-semibold text-gray-900 dark:text-gray-50'>
{t('project.journeys.otherPaths')}
</span>
<span className='shrink-0 text-gray-700 dark:text-gray-200'>
· {t('project.journeys.stepX', { x: node.step + 1 })}
</span>
</div>
<span className='shrink-0 font-mono font-semibold text-gray-900 tabular-nums dark:text-gray-50'>
{node.sessions.toLocaleString()}
</span>
</div>
<p className={`mt-1 ${tooltipLabelClassName}`}>
{t('project.journeys.hiddenPathsCount', { count: hiddenPaths })}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not show the global hidden-path count for each step.

hiddenPaths counts all omitted paths, including paths that never reached this node’s step. Later “Other paths” tooltips therefore overstate how many paths contribute to node.sessions. Use per-step path counts or remove the count from this tooltip.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/app/pages/Project/tabs/Journeys/JourneysView.tsx` around lines 703 - 725,
Remove the global hiddenPaths calculation and its hidden-path count from the
node.kind === 'other' tooltip in JourneysView. Keep the existing “Other paths”
label, step, and session count unchanged; do not display a count that is not
scoped to the current step.

Comment on lines +1197 to +1200
labelPosition='inside'
labelOrientation='horizontal'
labelPadding={10}
labelTextColor={theme === 'dark' ? '#e2e8f0' : '#334155'}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use contrasting label colors for light-theme nodes.

The dark #334155 text is rendered inside saturated blue, purple, and similar nodes, making several labels difficult to read. Select label color by node kind/background luminance or adjust the palette.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/app/pages/Project/tabs/Journeys/JourneysView.tsx` around lines 1197 -
1200, Update the labelTextColor configuration in the journey node rendering to
choose a contrasting color for light-theme nodes based on each node’s kind or
background luminance, ensuring labels remain readable on saturated blue, purple,
and similar backgrounds while preserving the existing dark-theme behavior.

@Blaumaus
Blaumaus merged commit 3980010 into main Jul 16, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant